package grade.calculate.event;

import grade.calculate.pojo.Data;
import grade.calculate.pojo.Student;
import grade.calculate.pojo.StudentGrade;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;

/**
 * 学业表现分计算
 * <p>计算的公式为 学业表现分 = ∑(i科目成绩 * i科目学分数) / 学分和</p>
 * <p>实现了{@link Event}接口 读取方法执行完后 监听器会按照添加事件的顺序 运行事件的run方法</p>
 *
 * @author fromdrowning
 * @date 2024/2/20 19:34
 */
@NoArgsConstructor
public class Calculate implements Event {

    /**
     * 计算结果保留的小数位数 默认保留两位小数
     */
    private Integer scale;

    /**
     * 从Excel中读的学生信息集合
     */
    private List<Student> studentList;

    /**
     * 计算后的学生学业表现分集合
     */
    private List<StudentGrade> studentGradeList;

    /**
     * 学业表现分计算
     * <P>读取方法执行完后 监听器会按照添加事件的顺序 运行run方法</P>
     *
     * @param data {@link Data}中可以获取到启动时配置的参数和其它事件处理结果数据
     */
    @Override
    public void run(Data data) {
        init(data);

        // 计算每一位学生的学业表现分
        studentList.forEach(student -> {
            // 计算包含体育和不包含体育的学业表现分
            Double gradeIncludeSport = halfUpResult(calculateGradeIncludeSport(student));
            Double gradeNoSport = halfUpResult(calculateGradeNoSport(student));

            StudentGrade studentGrade = StudentGrade.builder()
                    .name(student.getName())
                    .gradeIncludeSport(gradeIncludeSport)
                    .gradeNoSport(gradeNoSport)
                    .build();

            // 将结果添加到计算后的学生学业表现分集合中
            studentGradeList.add(studentGrade);
        });

        // 将计算后学生学业表现分集合设置到Data中 方便其它事件获取
        data.setStudentGradeList(studentGradeList);
    }

    /**
     * 初始化资源
     *
     * @param data {@link Data}中可以获取到启动时配置的参数和其它事件处理结果数据
     */
    private void init(Data data) {
        this.scale = data.getScale();
        this.studentList = data.getStudentList();
        this.studentGradeList = data.getStudentGradeList();
    }

    /**
     * 计算学业表现分和 包含体育
     * <p>计算的公式为 学业表现分 = ∑(i科目成绩 * i科目学分数) / 学分和</p>
     *
     * @param student 当前学生对象
     * @return {@link Double} 包含体育的学业表现分和
     */
    private Double calculateGradeIncludeSport(Student student) {
        Double totalCreditIncludeSport = calculateTotalCredit(student);

        Double gradeIncludeSport = student.getInfo().values().stream()
                .map(values -> values.get(0) * values.get(1) / totalCreditIncludeSport)
                .reduce(0.0, Double::sum);

        return gradeIncludeSport * 0.6;
    }

    /**
     * 计算学业表现分和 不包含体育
     * <p>计算的公式为 学业表现分 = ∑(i科目成绩 * i科目学分数) / 学分和</p>
     *
     * @param student 当前学生对象
     * @return {@link Double} 不包含体育的学业表现分和
     */
    private Double calculateGradeNoSport(Student student) {
        // 在遍历前先去掉体育
        student.getInfo().entrySet().removeIf(entry -> entry.getKey().contains("大学体育"));

        Double totalCreditNoSport = calculateTotalCredit(student);

        Double gradeNoSport = student.getInfo().values().stream()
                .map(values -> values.get(0) * values.get(1) / totalCreditNoSport)
                .reduce(0.0, Double::sum);

        return gradeNoSport * 0.6;
    }

    /**
     * 计算学分和
     *
     * @param student 当前学生对象
     * @return {@link Double} 学分和
     */
    private Double calculateTotalCredit(Student student) {
        return student.getInfo().values().stream()
                .map(values -> values.get(0))
                .reduce(0.0, Double::sum);
    }

    /**
     * 根据启动时配置保留小数的位数对计算结果保留小数并四舍五入
     *
     * @param result 计算的结果
     * @return {@link Double} 保留两位小数并四舍五入后的结果
     */
    private Double halfUpResult(Double result) {
        return new BigDecimal(String.valueOf(result)).setScale(scale, RoundingMode.HALF_UP).doubleValue();
    }
}
